package gofw_timewheel

import (
	"container/list"
	"container/ring"
	"time"
)

var (
	defaultInterval    = 1 * time.Second
	defaultBucketCount = 60
)

type TaskCallBack func()

type TimeWheel struct {
	interval time.Duration

	ticker  *time.Ticker
	buckets *ring.Ring

	stopChannel chan interface{}
	taskChannel chan interface{}
}

type Task struct {
	interval time.Duration
	callback TaskCallBack
}

func NewDefaultTimeWheel() *TimeWheel {
	return NewTimeWheel(defaultInterval, defaultBucketCount)
}

func NewTimeWheel(interval time.Duration, bucketCount int) *TimeWheel {
	timeWheel := &TimeWheel{
		interval:    interval,
		buckets:     ring.New(bucketCount),
		stopChannel: make(chan interface{}),
		taskChannel: make(chan interface{}),
	}
	for i := 0; i < bucketCount; i++ {
		timeWheel.buckets.Value = list.New()
		timeWheel.buckets = timeWheel.buckets.Next()
	}
	return timeWheel
}

func (t *TimeWheel) Start() {
	t.ticker = time.NewTicker(t.interval)
	go t.run()
}

func (t *TimeWheel) Add(tasks ...*Task) {
	if len(tasks) == 0 {
		return
	}
	for _, task := range tasks {
		t.taskChannel <- task
	}
}

func (t *TimeWheel) Stop() {
	t.stopChannel <- true
}

func (t *TimeWheel) run() {
	for {
		select {
		case item := <-t.taskChannel:
			if task := item.(*Task); task != nil {
				bucket := t.buckets.Move(int(task.interval.Nanoseconds()) / int(t.interval))
				if l := bucket.Value.(*list.List); l != nil {
					l.PushBack(task)
				}
			}
		case <-t.ticker.C:
			if l := t.buckets.Value.(*list.List); l.Len() > 0 {
				for element := l.Front(); element != nil; element = element.Next() {
					task := element.Value.(*Task)
					task.callback()
					bucket := t.buckets.Move(int(task.interval.Nanoseconds()) / int(t.interval))
					ll := bucket.Value.(*list.List)
					ll.PushBack(task)
				}
			}
			t.buckets.Value = list.New()
			t.buckets = t.buckets.Next()
		case <-t.stopChannel:
			t.ticker.Stop()
			return
		}
	}
}
